﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using log4net;
using Util;
using IAllianceSearchService = FlightSearchServer.AllianceServiceReference.ISearchService;

namespace FlightSearchServer
{
    internal class SearchManager
    {
        private readonly ILog log = LogManager.GetLogger(typeof(SearchManager));

        private readonly Dictionary<string, FlightAlliance> alliances = new Dictionary<string, FlightAlliance>();

        private readonly ConnectionsCache<IAllianceSearchService> connectionsCache = new ConnectionsCache<IAllianceSearchService>();

        [MethodImpl(MethodImplOptions.Synchronized)]
        public void AddAlliance(FlightAlliance alliance)
        {
            FlightAlliance old;
            if (alliances.TryGetValue(alliance.Name, out old))
            {
                connectionsCache.RemoveConnection(old.Uri);

                if (log.IsDebugEnabled)
                {
                    log.DebugFormat("Updating delegate URI of alliance \"{0}\" to: {1}", alliance.Name, alliance.Uri);
                }
            }
            else if (log.IsDebugEnabled)
            {
                log.DebugFormat("Registered new alliance \"{0}\" with delegate on URI: {1}", alliance.Name, alliance.Uri);
            }

            alliances[alliance.Name] = alliance;
        }

        public List<AvailableAirTravel> Search(string from, string to, DateTime date, string[] servers)
        {
            var serverNames = (servers != null && servers.Length > 0) ? string.Join(",", servers) : "ALL";

            if (log.IsInfoEnabled)
            {
                log.InfoFormat("Searching flights {0} => {1} on {2} (servers = {3})", from, to, date.ToShortDateString(), serverNames);
            }

            var sw = new Stopwatch();
            sw.Start();

            var availableTravels = new List<AvailableAirTravel>();

            List<FlightAlliance> alliancesList;
            lock (alliances)
            {
                alliancesList = alliances.Values.ToList();
            }

            foreach (var alliance in alliancesList)
            {
                try
                {
                    var connection = connectionsCache.GetConnection(alliance.Uri, TimeSpan.FromSeconds(1));
                    var res = connection.Channel.GetTravelOptions(from, to, date, servers);

                    var travels = from airTravel in res
                                  select new AvailableAirTravel
                                  {
                                      Price = airTravel.Price,
                                      Flights = new List<AvailableFlight>(
                                          from flight in airTravel.Flights
                                          select new AvailableFlight
                                          {
                                              From = flight.From,
                                              To = flight.To,
                                              Airline = flight.Airline,
                                              FlightId = flight.FlightId,
                                          }),
                                  };

                    availableTravels.AddRange(travels);
                }
                catch (Exception e)
                {
                    if (log.IsWarnEnabled)
                    {
                        log.Warn("Failed to query allince " + alliance.Name, e);
                    }

                    lock (alliances)
                    {
                        if (log.IsDebugEnabled)
                        {
                            log.Debug("Removing alliance " + alliance.Name);
                        }

                        alliances.Remove(alliance.Name);
                        connectionsCache.RemoveConnection(alliance.Uri);
                    }
                }
            }

            sw.Stop();

            if (log.IsInfoEnabled)
            {
                log.InfoFormat("Finished search for {0} => {1} on {2} (servers = {3}) in {4}sec (found {5} results)",
                    from, to, date.ToShortDateString(), serverNames, sw.Elapsed.TotalSeconds, availableTravels.Count);
            }

            return availableTravels;
        }
    }
}
